#include <netdb.h> 
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
# include <iostream>
#include <string>
#include <vector> 
#include <sstream>
#include "sequencer.h"

using namespace std;

bool DistChat:: endofObject = false;
int DistChat :: currentSequenceNum = 0;
int DistChat::seqno;
int DistChat::chatport;
string DistChat::hostIP;
char DistChat::userName[NAME_LEN];
bool DistChat:: firstMessage;
bool DistChat::isNxtSequencer;
string DistChat ::currentClientIP;
//map<long,string> DistChat::ipMap;
vector<string> DistChat:: iptables;
vector<string> DistChat:: currIPtable;
vector <Message> DistChat:: messageQueue;
int DistChat ::sock;
int DistChat::currentClientSequenceNumber;
vector <Message> DistChat :: sentMessages;

pthread_mutex_t mutexClient;


DistChat:: DistChat(){
	endofObject = false;
 isNxtSequencer = false;  
}

void DistChat::listen(int portNum)
{
 // cout <<"Withing listenign thread again!!!!!!11 "<<endl;
  int bytes_read;
  socklen_t addr_len;
  char recv_data[MAX_MSGSIZE];
  
  struct sockaddr_in server_addr , client_addr;
  
  char * ipbuf;
  char hostbuf[256];
  
  struct hostent *hostentry;
  int ret;
  
  ret = gethostname(hostbuf,sizeof(hostbuf));
  
  if(ret == -1){
    perror("gethostname");
    exit(1);
  }
  
  hostentry = gethostbyname(hostbuf);
  
  if(hostentry == NULL){
    perror("gethostbyname");
    exit(1);
  }
  
  ipbuf = inet_ntoa(*((struct in_addr *)hostentry->h_addr_list[0]));
  
  if(ipbuf == NULL){
    perror("inet_ntoa");
    exit(1);
  }
  //CHECK LATER: DO I NEED LOCKING????????
  currentClientIP = ipbuf;
  cout<<"IP address in listening thread is:"<<ipbuf<<endl;
  
  if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
    perror("Socket");
    exit(1);
  }
  int a = MAX_MSGSIZE;
  ret=setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &a, sizeof(int));
  if(ret == -1){
    perror("sock recv buf error");
    exit(1);
  }
  
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(portNum);
  server_addr.sin_addr.s_addr = INADDR_ANY;
  bzero(&(server_addr.sin_zero),8);
  
  
  if (bind(sock,(struct sockaddr *)&server_addr,
	   sizeof(struct sockaddr)) == -1)
    {
      perror("Bind");
      exit(1);
    }
  
  addr_len = sizeof(struct sockaddr);
  
  cout << "\nUDPServer Waiting for client " << portNum;
  fflush(stdout);
  
  while (1)
    { 
       sleep(0);
      bytes_read = recvfrom(sock,recv_data,MAX_MSGSIZE,0,
			    (struct sockaddr *)&client_addr, &addr_len);
      recv_data[bytes_read] = '\0';
      pthread_mutex_lock(&mutexClient);
      string temp;
      istringstream ss (recv_data);
      Message m;
      int i=0, seq=0,type=0,instanceNum;
      string strmsg,struname;

      while(getline(ss,temp,'|'))
      {
      	  switch(i){
      	  case 0:
      	  seq=atoi(temp.c_str());
      	  break;
      	  case 1:
      	  struname=temp;
      	  break;
      	  case 2:
      	  strmsg=temp;
      	  break;
      	  case 3:
      	  instanceNum = atoi(temp.c_str());
      	  break;
      	  case 4:
      	  type=atoi(temp.c_str());
      	  break;
      	  }
      	  i++;
      	  
      }
      m.seqno=seq;
      m.username=struname;
      m.message=strmsg;
      m.messagetype=type;
      m.instancenumber = instanceNum;
      // cout << "I "<< DistChat ::currentClientIP << " received a message!! :  "<< m.messagetype << endl; 
      //cout << "Sequencenumber assigned by sequencer : " << m.seqno << ". Current Sequence no : " << currentSequenceNum <<" : "<<m.messagetype <<endl;
      
      if(m.messagetype == MSG_SERVERCHECKSALIVE)
	{
	  //Got response back from server
	  //  cout <<"Ok Server - you are alive : "<<Client::currentTryNum <<endl;	
	  Client::checkServerAlive[m.instancenumber%TRIES] = true;
	}
      else if(m.messagetype == MSG_REBOOT)
	{
	 // cout <<"Got message to re-boot!. Re-booting right away! " <<endl;
	}
      else if(m.messagetype == MSG_SAFETOKILLSELFNOW)
	{
	  isNxtSequencer = true;
	  DistChat :: sentMessages.clear();
	  DistChat :: currentClientSequenceNumber = 0;
	//  cout <<"REMOVING SELF FROM IPTABLE "<<endl;
 	  for(int n=0;n<currIPtable.size();n++)
	    {
	      if(currIPtable[n].compare(DistChat::currentClientIP)==0)
		{		
		  currIPtable.erase(currIPtable.begin()+n);
		  break;
		}
	    }
	  pthread_mutex_unlock(&mutexClient);
	  break;
	}
	else if(m.messagetype == MSG_NOWSAFETOKILLSELFNOW)
	{
	endofObject = true;
	  isNxtSequencer = false;
	  DistChat :: sentMessages.clear();
	  DistChat :: currentClientSequenceNumber = 0;
	//  cout <<"REMOVING SELF FROM IPTABLE "<<endl;
 	  for(int n=0;n<currIPtable.size();n++)
	    {
	      if(currIPtable[n].compare(DistChat::currentClientIP)==0)
		{		
		  currIPtable.erase(currIPtable.begin()+n);
		  break;
		}
	    }
	  pthread_mutex_unlock(&mutexClient);
	  break;
	}
       else if (m.messagetype ==  MSG_CHECKNEXTSERVERALIVE)
	 {
	   //Just reply back!
	   Message replyClient;
	   replyClient.messagetype = MSG_CHECKNEXTSERVERALIVEREPLY;
	   char* argv[3];
	   argv[0]="abcd\0";
	   string str = m.message;
	   char * writable = new char[str.size() + 1];
	   std::copy(str.begin(), str.end(), writable);
	   writable[str.size()] = '\0';
	   argv[1]=writable;
	   char num[5];
	   sprintf(num, "%d", chatport);
	   argv[2]=num;
	   
	   replyClient.instancenumber = m.instancenumber;
	   stringstream temp(stringstream::in | stringstream::out);
	   temp<<replyClient;
	   send(argv,temp.str().c_str());
	   delete[] writable;
	 }
      else if (m.messagetype ==  MSG_CHECKNEXTSERVERALIVEREPLY)
	{
	    Client:: checkNextServerAlive[m.instancenumber] = true;
	}
      else if(m.messagetype == MSG_UPDATECURRENTSEQUENCER)
	{
	  //Sequencer is found....now update the class variable that sequencer is found
	//  cout <<"TIME TO UPDATE CURRENT sEQUENCER!!!!!!!!" <<endl;
	  Client::isSequencerFound = true;	  
	  hostIP = m.message;
	  DistChat :: sentMessages.clear();
	  DistChat :: currentClientSequenceNumber = 0;
	  
	  //close(DistChat::sock);
	  alarm(0);
	  bool found = false;
	  int indTmp = 0;
	  for(indTmp = 0 ; indTmp < currIPtable.size(); indTmp++)
	    {
	      if(currIPtable[indTmp].compare(m.message) == 0)
		{
		  found = true;
		  break;
		}
	      
	    }
	  if(found == true)
	    {
	     //  cout <<"Removing sequencer IP: "<<m.message <<endl;
	       currIPtable.erase(currIPtable.begin() + indTmp);
	    }

	  pthread_mutex_unlock(&mutexClient);
	  break;
	}
      else if(m.messagetype == MSG_ASKCLIENTRETRANSMIT)
	{
	  Message resendData;
	  int sequenceNumOfData = m.seqno;
	  
	  for(int idTmpNum = 0; idTmpNum < DistChat :: sentMessages.size(); idTmpNum++)
	    {
	      if(DistChat :: sentMessages[idTmpNum].seqno == sequenceNumOfData)
		{
		  //Send this data to host
		  char* argv[3];
		  argv[0]="abcd\0";
	
		  string str = hostIP;
		  char * writable = new char[str.size() + 1];
		  std::copy(str.begin(), str.end(), writable);
		  writable[str.size()] = '\0';
		  argv[1]=writable;
		  char num[5];
		  sprintf(num, "%d", chatport);
		  argv[2]=num;

		  Message replyWithData;
		  replyWithData.seqno = sequenceNumOfData;
		  replyWithData.username = userName;

		  replyWithData.message =  sentMessages[idTmpNum].message;
		  replyWithData.messagetype = MSG_DATAMSG;
		  
		  stringstream temp(stringstream::in | stringstream::out);
		  temp<<replyWithData;
		  send(argv,temp.str().c_str());
		  delete[] writable;

		}
	    }
	  
	}
	
      else if(m.messagetype == MSG_ADDIPTOTABLE)
	{
	//  cout <<"Received msg to add IP : " << m.message <<endl;
	  bool found = false;
	  for(int ind = 0 ; ind < currIPtable.size(); ind++)
	    {
	      if(currIPtable[ind].compare(m.message) == 0)
		{
		  found = true;
		  break;
		}
	      
	    }
	  if(found == false)
	    {
	       cout <<"Adding IP: "<<m.message <<endl;
	       currIPtable.push_back(m.message);
	    }
	}
      else if(m.messagetype == MSG_REMOVEFROMIPTABLE)
	{
	  cout << "Removing IP : " << m.message <<endl;
	  for(int j =0; j < currIPtable.size(); j++)
	    {
	      if(currIPtable[j].compare(m.message) == 0)
		{
		//cout <<"Successfully removing IP : " <<m.message <<endl;
		  currIPtable.erase(currIPtable.begin() + j);
		  break;
		}
	    }
	}
      else if(m.messagetype == MSG_DATAMSG)
	{
	  
	  if(firstMessage == true)
	    {
	      currentSequenceNum = m.seqno;
	      firstMessage = false;
	    }
	  //cout <<"GOT A DATA MESSAGE !!!!!" <<endl;
	  
	  if(currentSequenceNum == m.seqno)
	    {
          cout<<m.username<<" says : ";
          cout<<m.message<<endl;
	  currentSequenceNum++;
	  while(messageQueue.size() != 0)
	    {
	      bool nextMessagePresent = false;
	      for(int i = 0; i < messageQueue.size(); i++)
		{
		  if(messageQueue[i].seqno == currentSequenceNum)
		    {
		      cout<<messageQueue[i].username<<" says : "<<messageQueue[i].message<<endl;
		      currentSequenceNum++;
		      messageQueue.erase (messageQueue.begin() + i);
		      nextMessagePresent = true;
		    }
		}
	      if(nextMessagePresent == false)
		break;
	      //Messages are present in the message queue.
	    }
	  //Now check messageQueue if any messages came with higher sequence numbers than this and print them and remove from messageQueue
	  
	}
      	else if(m.seqno > currentSequenceNum)
	{
	  printf("MISSED MESSAGE!!!!!!!!!!!!!!!!!!!!OOPSSSSSSSSS");
	  //Missed messages
	  //Add this message on  message queue
	  messageQueue.push_back(m);
	  //Request messages to be retransmitted from currentSequenceNum to m->seqno
	 // cout << "Current Sequence number : " << currentSequenceNum <<endl;
	//  cout << "Curent message Sequnence number : " <<m.seqno <<endl;
	  int counterTemp = 1;

	  for(int i = currentSequenceNum; i < m.seqno; i++)
	    {
	     // cout << "Counter is : "<< counterTemp <<endl;
	      //Ask sequencer to re-transmit the messages with these sequence numbers.
	      char* argv[3];
	      argv[0]="abcd\0";
	      string str=hostIP;
	      char * writable = new char[str.size() + 1];
	      std::copy(str.begin(), str.end(), writable);
	      writable[str.size()] = '\0';
	      argv[1]=writable;
	      char num[5];
	      sprintf(num, "%d", chatport);
	      argv[2]=num;

	      Message mrequestTransmission;
	      mrequestTransmission.messagetype = MSG_RETRANSMITDATA;
	      mrequestTransmission.seqno = i;
	      stringstream temp(stringstream::in | stringstream::out);
	      temp<<mrequestTransmission;
	      send(argv,temp.str().c_str());
	      delete[] writable;

	    }

	}
      	else
	{
	 // cout <<"OOPSSSSSSSSSS"<<endl;
	  //Same message was transmitted twice. Do nothing in this case
	}    
      
      	fflush(stdout);
      }
      else
	{
	 // cout <<"Message type undefined!!!"<<m.messagetype <<endl;
	}
       pthread_mutex_unlock(&mutexClient);
      
    }
}

void DistChat::send(char * argv[], const char* send_data)
{
  int sock;
  struct sockaddr_in server_addr;
  struct hostent *host;
  
  host= (struct hostent *) gethostbyname((char *)argv[1]);
  
  
  if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
      perror("socket");
      exit(1);
    }
  int portNum = atoi(argv[2]);
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(portNum);
  server_addr.sin_addr = *((struct in_addr *)host->h_addr);
  bzero(&(server_addr.sin_zero),8);
   
 sendto(sock, send_data, strlen(send_data), 0,
	 (struct sockaddr *)&server_addr, sizeof(struct sockaddr));
	 
}

char * DistChat::getIPAddress(){
  char * ipbuf;
  struct hostent *hostentry;
  int ret;
  char hostbuf[256];    
  ret = gethostname(hostbuf,sizeof(hostbuf));
  hostentry = gethostbyname(hostbuf);
  
  if(hostentry == NULL){
    perror("gethostbyname");
    exit(1);
  }
  ipbuf = inet_ntoa(*((struct in_addr *)hostentry->h_addr_list[0]));
  
  if(ipbuf == NULL){
    perror("inet_ntoa");
    exit(1);
  }
  
  cout<<"IP address:"<<ipbuf<<endl;
  return ipbuf;	
  
}

void DistChat::broadcast(Message m){
  char *argv[3];
  argv[0]=".";
  char num[5];
  sprintf(num, "%d", chatport);
  argv[2]=num;	
  for(int i=0;i<iptables.size();i++){
    string str=iptables[i];	
    char * writable = new char[str.size() + 1];
    std::copy(str.begin(), str.end(), writable);
    writable[str.size()] = '\0';
    char buffer[MAX_MSGSIZE];
     		    
    argv[1]=writable;
    stringstream temp (stringstream::in | stringstream::out);
    temp<<m;
    send(argv,temp.str().c_str());	
    delete[] writable;
    
  } 
}

DistChat::~DistChat()
{
  cout <<"DistChat destructor called for this object instance" <<endl;
  //close(sock);
  // ~Client();
}

int main(int argc, char* argv[])
{
  Client *dchatInstance;
  char send_data[1024];
  char * ipbuf;
  
  if(argc == 3){
    printf("Comes here- Start");
    Client *c;
    c = new Sequencer(argv);    
       
    cout<<"IP address:"<<ipbuf<<endl;
    printf ("Ending program!");
  }
  else if(argc == 4){
    dchatInstance = new Client(argv);
    cout<<"Now creating new instance of sequencer---------------->"<<endl;
    vector<string> ips = dchatInstance->currIPtable;
    bool isNxtSequencer = dchatInstance->isNxtSequencer;
    string nextHostIP = dchatInstance -> hostIP;
    bool endObj = dchatInstance->endofObject;
    //Client* c= (Client*)dchatInstance;
    
    //delete c;
     delete  dchatInstance;
     if(endObj == false)
    {
      if(isNxtSequencer)
      {
	cout <<"initializing new sequencer!!!!!! " <<endl;
	dchatInstance=new Sequencer(argv,ips);
      }
      else
      {
	cout <<"Restarting as a client now!!! "<<endl;
	char * writableTemp = new char[nextHostIP.size() + 1];
	std::copy(nextHostIP.begin(), nextHostIP.end(), writableTemp);
	writableTemp[nextHostIP.size()] = '\0';
	argv[2] = writableTemp;
	main(4, argv);
	delete[] writableTemp;
      }
    }
  }
  return 0;
}
